package com.piper.spring.servlet;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.piper.spring.annotation.Autowired;
import com.piper.spring.annotation.Controller;
import com.piper.spring.annotation.RequestMapping;
import com.piper.spring.annotation.Service;
import com.piper.spring.aop.AopConfig;
import com.piper.spring.aop.MyMethodInterceptor;
import com.piper.spring.model.RequestData;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class DispatchServlet extends HttpServlet {

    //传入参数
    private static final String LOCATION = "contextConfigLocation";

    //所有配置信息
    private Properties properties = new Properties();

    //保存所有扫描到的类名
    private List<String> classNames = new ArrayList<>();

    //保存初始化bean  面向javabean编程
    private Map<String, Object> ioc = new HashMap<>();

    //保存URL和接口映射关系
    private Map<String, Method> handlerMapping = new HashMap<>();

    //key方法的全路径名 value方法对应的切面类
    public static Map<String, TreeSet<AopConfig>> aopContainer = new HashMap<>();

    public static Map<String, TreeSet<AopConfig>> getAopContainer() {
        return aopContainer;
    }

    public DispatchServlet() {
        super();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1，加载配置文件
        doLoadConfig(config.getInitParameter(LOCATION));
        //2，扫描相关类
        doScanner(properties.getProperty("scanPackage"));
        //3，初始化所有相关类的实例，保存到ioc容器
        doInstance();
        //4，依赖注入
        doAutowired();
        //5，请求映射
        initHandlerMapping();
        //6，扫描aop配置类
        doScanAop(properties.getProperty("scanPackage"));
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            req.setCharacterEncoding("UTF-8");
            resp.setCharacterEncoding("UTF-8");
            resp.setContentType("text/html;charset=UTF-8");
            this.doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception, Details:\r\n" +
                    Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ""));
        }
    }

    /**
     * 将文件读取到Properties对象中
     *
     * @param location
     */
    private void doLoadConfig(String location) {
        InputStream is = null;
        try {
            is = this.getClass().getClassLoader().getResourceAsStream(location);
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 递归扫描出所有的Class文件
     *
     * @param packageName
     */
    private void doScanner(String packageName) {
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getPath());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(packageName + "." + file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replace(".class", "").trim());
            }
        }
    }

    /**
     * 初始化所有相关的类，并放入到IOC容器之中
     */
    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Controller.class)) {
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service service = clazz.getAnnotation(Service.class);
                    String beanName = service.value();

                    //获得代理类
                    Object o = MyMethodInterceptor.getMethodProxy(clazz);

                    //使用设置的名称
                    if ("".equals(beanName.trim())) {
                        ioc.put(lowerFirstCase(clazz.getSimpleName()), o);
                    } else {
                        ioc.put(beanName, o);
                    }

                    //如果没设置名称，按照接口类型创建一个实例
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(), o);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * IOC容器中的类，需要赋值的字段进行赋值
     */
    private void doAutowired() {
        if (ioc.isEmpty()) return;

        Collection<Object> beans = ioc.values();
        for (Object bean : beans) {
            Field[] fields = bean.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                //是否需要自动注入
                if (!field.isAnnotationPresent(Autowired.class)) continue;
                Autowired autowired = field.getAnnotation(Autowired.class);
                //获取注入的beanName
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }
                try {
                    //从ioc容器拿到bean注入
                    field.set(bean, ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    /**
     * RequestMapping中配置的信息和Method进行关联
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) return;
        Collection<Object> beans = ioc.values();
        for (Object bean : beans) {
            Class<?> clazz = bean.getClass();
            if (!clazz.isAnnotationPresent(Controller.class)) continue;
            //获取controller的url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                baseUrl = requestMapping.value();
            }
            //获取metho的url
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)) continue;
                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
            }
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        if (this.handlerMapping.isEmpty()) return;

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replace("/+", "/");

        //请求文件 直接返回
        if (url.endsWith(".html") || url.endsWith(".jsp") || url.endsWith(".js")) {
            String realPath = req.getSession().getServletContext().getRealPath(url);
            outputFile(realPath, resp.getOutputStream());
            return;
        }

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().println("404 Not Fount");
            return;
        }

        Method method = this.handlerMapping.get(url);
        Class<?>[] parameterTypes = method.getParameterTypes();
        Map<String, String[]> parameterMap = req.getParameterMap();
        Object[] paramValus = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            Class prmType = parameterTypes[i];
            if (prmType == RequestData.class) {
                JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(parameterMap));
                paramValus[i] = new RequestData(req.getRemoteHost(), url, jsonObject);
            } else if (prmType == HttpServletRequest.class) {
                paramValus[i] = req;
            } else if (prmType == HttpServletResponse.class) {
                paramValus[i] = resp;
            }
        }

        try {
            String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
            //利用反射机制调用方法
            Object o = this.ioc.get(beanName);
            method.invoke(o, paramValus);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出 文件
     *
     * @param filePath
     * @param outputStream
     */
    private void outputFile(String filePath, ServletOutputStream outputStream) {
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
            bufferedOutputStream = new BufferedOutputStream(outputStream);

            int len;
            byte[] buf = new byte[1024];
            while ((len = bufferedInputStream.read(buf)) >= 0) {
                bufferedOutputStream.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedInputStream != null) {
                    bufferedInputStream.close();
                }
                if (bufferedOutputStream != null) {
                    bufferedOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 扫描aop配置类 todo 如果为空 如何处理
     *
     * @param packageName
     */
    private void doScanAop(String packageName) {
        if (null == packageName || "".equals(packageName)) return;

        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getPath());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                doScanAop(packageName + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                try {
                    Class<?> aopClazz = Class.forName(packageName + "." + file.getName().replace(".class", "").trim());
                    if (aopClazz.isInterface()) continue;
                    Class<?> superclass = aopClazz.getSuperclass();
                    if (!superclass.getName().equals("com.piper.spring.aop.AopConfig")) continue;
                    resolveAop(aopClazz);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 解析aop配置类
     *
     * @param aopClazz
     */
    private void resolveAop(Class<?> aopClazz) throws Exception {
        AopConfig ac = (AopConfig) aopClazz.newInstance();
        String pointCut = ac.pointCut();
        if (null == pointCut || "".equals(pointCut)) return;

        pointCut = pointCut.replaceAll(" +", " ");

        String[] cuts = pointCut.split(" ");
        if (cuts.length != 3) {
            System.err.println("aop配置类：" + aopClazz.getName() + "的切点表达式不合法");
        }

        putMth(cuts[0], cuts[1], cuts[2], ac);
    }

    private void putMth(String pkgName, String clzName, String mthName, AopConfig ac) throws Exception {
        URL url = this.getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));
        File dir = new File(url.getPath());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(pkgName + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String fileName = file.getName().replace(".class", "");
                String clzname = clzName.replace("*", "");
                boolean flg = false;
                if ("*".equals(clzName)) {
                    flg = true;
                } else if (clzName.startsWith("*") && fileName.endsWith(clzname)) {
                    flg = true;
                } else if (clzName.endsWith("*") && fileName.startsWith(clzname)) {
                    flg = true;
                } else if (fileName.equals(clzname)) {
                    flg = true;
                }

                if (flg) {
                    Class<?> clz = Class.forName(pkgName + "." + fileName);
                    String mtnm = mthName.replace("*", "");
                    Method[] methods = clz.getDeclaredMethods();
                    for (Method method : methods) {
                        boolean mfl = false;
                        if ("*".equals(mthName)) {
                            mfl = true;
                        } else if (mthName.startsWith("*") && method.getName().endsWith(mtnm)) {
                            mfl = true;
                        } else if (mthName.endsWith("*") && method.getName().startsWith(mtnm)) {
                            mfl = true;
                        } else if (method.getName().equals(mtnm)) {
                            mfl = true;
                        }

                        if (mfl) {
                            String key = clz.getName() + "." + method.getName();
                            TreeSet<AopConfig> aopConfigs = aopContainer.get(key);
                            if (aopConfigs == null) {
                                TreeSet<AopConfig> mtset = new TreeSet<>();
                                mtset.add(ac);
                                aopContainer.put(key, mtset);
                            } else {
                                aopConfigs.add(ac);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 首字母小写
     *
     * @param str
     * @return
     */
    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
